package com.curlap.orb.servlet;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.curlap.orb.io.AbstractSerializer;
import com.curlap.orb.io.SerializerException;
import com.curlap.orb.io.SerializerFactory;
import com.curlap.orb.io.CurlSerializableStreamWriter;

/**
 * DefaultInstanceManagementFilter
 */
public class DefaultInstanceManagementFilter implements Filter
{
    private final Log log = LogFactory.getLog(DefaultInstanceManagementFilter.class);
    private AbstractSerializer serializer = null;

    public void init(FilterConfig config) throws ServletException 
    {
        serializer = SerializerFactory.getInstance().getSerializer();
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
    {
        // accept just HTTP.
        if (!((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)))
            throw new ServletException("Need to use the HTTP.");

        final HttpServletRequest httpRequest = (HttpServletRequest)request;
        InstanceManagementRequest instanceManagementRequest = null;
        // serialize the request object and set to the attribute
        try
        {
            if (request.getContentLength() == -1)
            {
            	// forward to welcome page.
            	request.getRequestDispatcher("/index.html").forward(request, response);
            	return;
            }
            instanceManagementRequest = 
                (InstanceManagementRequest)serializer.deserialize(httpRequest.getInputStream());
            handleStreamResponse(instanceManagementRequest, (HttpServletResponse)response);
            httpRequest.setAttribute(InstanceManagementServlet.REQUEST_OBJECT, instanceManagementRequest);
        }
        catch (SerializerException e)
        {
            // NOTE: 
            //  Cannot handle the exception when "stream response" mode. 
        	//  Clients cannot receive the error.
            setResponse((HttpServletResponse)response, e);
            return;
        }

        // do filters and servlets
        chain.doFilter(httpRequest, response);

        // deserialize the object and return the response
        if (!isStreamResponse(instanceManagementRequest))
        {
            setResponse(
                    (HttpServletResponse)response,
                    httpRequest.getAttribute(InstanceManagementServlet.RESPONSE_OBJECT));
        }
    }

    public void destroy() 
    {
        // do nothing
    }

    // - - protected method - -
    protected void setResponse(HttpServletResponse response, Object obj)
    {
        if (obj instanceof Exception)
        {
            setFailedResponse((HttpServletResponse)response, (Exception)obj);
        }
        else
        {
            try
            {
                setSucceededResponse((HttpServletResponse)response, obj);
            }
            catch (InstanceManagementException e) 
            {
                setFailedResponse((HttpServletResponse)response, e);
            }
        }
    }

    // Response the succeeded response 
    protected void setSucceededResponse(HttpServletResponse response, Object obj) throws InstanceManagementException
    {
        OutputStream ostream = null;
        try
        {
            response.setStatus(200);
            ostream = response.getOutputStream();
            serializer.serialize(obj, ostream);
            log.debug("Response data [" + obj + "]");
        }
        catch (IOException e)
        {
            setFailedResponse(response, e);
        }
        catch (SerializerException e)
        {
            setFailedResponse(response, e);
        }
        finally 
        {
            if (ostream != null)
            {
                try 
                {
                    ostream.close();
                }
                catch (IOException e)
                {
                    setFailedResponse(response, e);
                }
            }
        }
    }

    // Response the failed response
    protected void setFailedResponse(HttpServletResponse response, Exception exception)
    {
        OutputStream ostream = null;
        try
        {
            response.setStatus(200);
            ostream = response.getOutputStream();
            serializer.serialize(new ExceptionContent(exception), ostream);
            log.debug(exception.getMessage(), exception);
        }
        catch (InstanceManagementException e)
        {
        	response.setStatus(500);
        	log.debug(e.getMessage(), e);
        }
        catch (SerializerException e)
        {
            response.setStatus(500);
            log.debug(e.getMessage(), e);
        }
        catch (IOException e)
        {
            response.setStatus(500);
            log.debug(e.getMessage(), e);
        }
        finally 
        {
            if (ostream != null)
            {
                try 
                {
                    ostream.close();
                }
                catch (IOException e)
                {
                    setFailedResponse(response, e);
                }
            }
        }
    }
    
    // for stream response
    private boolean isStreamResponse(InstanceManagementRequest request)
    {
        Map<Object, Object> header = request.getHeader();
        return (header.containsKey(InstanceManagementServlet.STREAM_RESPONSE) && (Boolean)header.get(InstanceManagementServlet.STREAM_RESPONSE));
    }
    
    private void handleStreamResponse(InstanceManagementRequest request, HttpServletResponse response) throws IOException, SerializerException
    {
        if (isStreamResponse(request))
        {
            if (request instanceof InvokeRequest)
            {
                InvokeRequest invokeRequest = (InvokeRequest)request;
                invokeRequest.setArguments(addObjectToArray(
                        invokeRequest.getArguments(), 
                        new CurlSerializableStreamWriter(response.getOutputStream())));
            }
            else if (request instanceof InvokeInContextRequest)
            {
                InvokeInContextRequest invokeRequest = (InvokeInContextRequest)request;
                invokeRequest.setArguments(addObjectToArray(
                        invokeRequest.getArguments(), 
                        new CurlSerializableStreamWriter(response.getOutputStream())));
            }
        } 
        else { /* skip */ }
    }
    
    private Object[] addObjectToArray(Object[] src, Object addedObject)
    {
        Object[] dist = null;
        if (src == null)
            dist = new Object[1];
        else 
        {
            dist = new Object[src.length + 1];
            System.arraycopy(src, 0, dist, 0, src.length);
        }
        dist[dist.length - 1] = addedObject;
        return dist;
    }
}